//============================================================================
//
// saHostImport.js
// ---------------
//
// Import a host
//
//============================================================================

var fs = require('fs'),
	path = require('path'),
	config = require('jsonconfig'),
	sqlite3 = require('sqlite3').verbose();

var this_host = {};
	// This is a global scope object used to store information about the
	// host we're importing.
	// audit_ver is the version of the audit file
	// audit_time is the timestamp of the audit file
	// audit_host is the hostname of the global zone
	// audit_plat is the virtualization type of the global zone
	// zonedata is an object of zones, each zone being an object of classes
	// host_id is the id value for the host in the hosts table

// Load in the config file so we can know which versions of the s-audit
// client we support.

config.load(['config/config.json']);

// Make a database connection

var db = new sqlite3.Database(config.DB, function() {
    db.run('PRAGMA foreign_keys=on');
	}
);

//----------------------------------------------------------------------------
// WORK IN PROGRESS

var importZones = function() {

	var zone,		// the name of the zone we're working on
		zdat,		// all data for the current zone
		zstat,		// zone status if not running
		zones = []; // a simple array of the zones in the host that can be
					//shifted away
	
	var results = [];

	for (zone in this_host.zonedata) {
		zones.push(zone);
	}

	function final() {
		console.log('done');
	};

	function series(zone) {

		if (zone) {

			zdat = this_host.zonedata[zone];
		
			// If the zone was up and running, it'll have a virtualization
			// type, which we need to look up, or put into, the plat_types
			// table. If it wasn't running, it'll have a status, which goes
			// in the zone_status table.
		
			if (zdat.platform.virtualization) {
				getPlatTypeID(zdat.platform.virtualization,
				function(plat_type)
				{
					db.run('INSERT INTO zones VALUES(null, ?, ?, ?, null)',
					[ zone, plat_type, this_host.host_id], function() {
						return series(zones.shift());
					});
				});
			}
			else {
				zstat = zdat.platform['zone status'];

				getID('zone_status', 'status_name', zstat,
				"null, '" + zstat + "'",
				function(status) {
					db.run('INSERT INTO zones VALUES(null, ?, null, ?, ?)',
					[ zone, this_host.host_id, status], function() {
						return series(zones.shift());
					});
                });

			}
		}
		else {
			return final();
		}

	};

	series(zones.shift());
};

//----------------------------------------------------------------------------
// EXPORTED METHODS

module.exports = {

doImport: function(group, file, callback) {

	// First thing is make sure the target group exists, and get its ID from
	// the database

	// 01
	// Called from CLI/WWW layer
	// Calls readFile()

	try {
		db.get('SELECT id FROM audit_groups WHERE name = ?', group,
		function(err, res) {

			if (err) {
				throw(err);
			}
			else if (res === undefined) {
				throw('Group does not exist.');
			}
			else {
				// We've got a group ID. Store it in the 'this_host' object,
				// and call the method which reads in the file

				this_host.gid = res.id;
				readFile(file);
			}

		});
	}
	catch(err) {
		console.log('ERROR: ' + err);
	}

}

}; // close exports

//----------------------------------------------------------------------------
// PRIVATE METHODS

// The functions down here are the strings of spaghetti that make this work.
// I've tried to clearly document the order in which they're called and the
// caller and callee of each method.

var readFile = function(file) {
	
	// Read in the file. It may be compressed, and we have a separate
	// function to handle that. Either branch will end up calling the
	// parseJSON() method.

	// 02
	// Called from doImport()
	// Calls uncompressJSON or parseJSON

	file = path.resolve(file);

	if (fs.existsSync(file)) {

		if (file.match(/\.gz$/)) {
			fs.readFile(file, uncompressJSON);
		}
		else {
			fs.readFile(file, 'utf8', parseJSON);
		}

	}
	else {
		throw("File '" + file + "' does not exist.");
	}
	
};

var uncompressJSON = function(err, data) {

	// 03
	// called from readFile if file is compressed
	// calls parseJSON

	// Uncompress a gzipped buffer and call the parseJSON() method, just
	// like doImport() does.

	zlib = require('zlib');

	zlib.gunzip(data, function(err, buf) {
		parseJSON(err, buf.toString());
	});

};

var parseJSON = function(err, data) {

	// Read in the file and populate 'whole' and 'header' class variables

	// 04
	// called from uncompressJSON() or readFile()
	// calls setupHostInDB()

	var header,	// a couple of shorthand variables
		dc;

	if (whole = JSON.parse(data)) {
		
		// Separate the header from the zone data

		header = whole.header;
		this_host.zonedata = whole;
		delete(this_host.zonedata.header);

		// do some basic validation. First, does it actually look like a
		// Fourdit file?

		if (header.sauditver === undefined) {
			throw('Not a Fourdit file.');
		}

		// Was it produced by an agent version that we're supposed to
		// understand?

		if (header.sauditver < config.min_client_ver || header.sauditver >
			config.max_client_ver) {
			throw('Unsupported file version. (' + header.sauditver + ')');
		}	

		// Make sure there's a hostname and a starttime

		if (header.hostname === undefined)
			throw('header does not contain a hostname value.');

		if (header.starttime === undefined)
			throw('header does not contain a starttime value.');

		// It all looks pretty good. Record some information. First, get the
		// audit time from the header. This is the value we use to update
		// the database, not the "audit complete" values in the audit files.
		// Unix date(1) thinks January is month 1, Javascript thinks it's
		// month 0.  This number goes in the 'checkpoints' table 'timestamp'
		// field later.

		dc = header.starttime.split(' ');

		this_host.audit_time = new Date(dc[0], (dc[1] - 1), dc[2], dc[3],
		dc[4]).getTime();

		this_host.audit_ver = header.sauditver;
		this_host.audit_host = header.hostname;

		// There ought to be a virtualization type too. If not, 'undefined'
		// will be fine. We're going to pass that through to
		// getPlatTypeID(). That method needs to know what to call next.
		// It's getHostID(). Not nice, I know, but things will get worse.

		getPlatTypeID(whole.global.platform.virtualization, getHostID)
	}
	else {
		throw('Unable to parse JSON. Error in source file?');
	}

};

var getID = function(table, column, value, sql_values, callback) {

	// Call this with a table, column, and value, and if the entry already
	// exists, it will return the value of the ID field for the row. If not,
	// it will insert the values given in the sql_values field.

	// This gets called from a number of places, and it in turn calls
	// whatever is passed as the 'callback' argument.

	// First, try to select. It doesn't look like you can use the SQLite ?
	// subsitutions in this case, so I'll make these statements by hand.

	db.get('SELECT id FROM ' + table + ' WHERE ' + column + ' = ?',  value, 
	function(err, row) {

		if (err) {
			throw("Unable to query '" + table + "' table.\n" + err);
		}
		else if (row === undefined) {

			// Nothing matched, so do the INSERT

			db.run('INSERT INTO ' + table + ' VALUES(' + sql_values + ')',
			function(err, res) {

				if (err) {
					throw("Error INSERTing '" + sql_values + "' into '" +
					table + "' table.\n" + err);
				}
				else {
					callback(this.lastID);
				}

			});
		}
		else {
			callback(row.id);
		}

	});

};

var getPlatTypeID = function(plat_type, next_fn) {

	// calls getID to get the ID of the platform type we have been passed

	//plat_type = plat_type.replace(/ \(.*/, '');

	// 05
	// called from parseJSON, calls getID() with the name of the next
	// function in the chain, which is getHostID

	getID('plat_types', 'type_name', plat_type,
	"null, '" + plat_type + "'",
	next_fn);
};

var getHostID = function(plat_id) {
	
	// Gets the host ID with the getID() method.

	// 06 
	// called as a callback from getID() via getPlatTypeID(). Gets the
	// database ID for this host using getID(), which it tells to pass the
	// result to checkpointHost()

	getID('hosts', 'hostname', this_host.audit_host,
	"null,'" + this_host.audit_host + "', " + this_host.gid + "," + plat_id,
	checkpointHost);
};

var checkpointHost = function(host_id) {
	
	// Insert the host ID and timestamp into the 'checkpoint' table. If
	// we already have this checkpoint, throw an exception. Checkpoints are
	// key/value pairs of host and audit time, so they uniquely identify an
	// audit file.

	// 07
	// called from getHostID() via getID(). Calls importZones() from the
	// callback.

	// put the host's database ID in the this_host object because the zones
	// need to know to whom they belong

	this_host.host_id = host_id;

	db.run('INSERT INTO checkpoints VALUES(?, ?)', host_id,
	this_host.audit_time, function(err, res) {

		if (err) {

			if (err.code === 'SQLITE_CONSTRAINT') {
				throw('This datafile has already been imported.');
			}
			else {
				throw('Unable to update checkpoint.\n' + err);
			}

		}
		else {
			importZones();

		}

	});

};
